/*
 * stura_voting.js
 *
 * Copyright (C) 2014 Fabian Wenzelmann
 *
 * This file is part of stura_voting.
 *
 * stura_voting is free software: you
 * can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation,
 * either version 3 of the License, or (at your option) any later version.
 *
 * stura_voting is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 * Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with stura_voting.
 *
 * If not, see <http://www.gnu.org/licenses/>.
 */

$(document).ready(function() {
  $('#schulze_tabs').tabs({active: 0});
  $('#options').tabs({active: 0});

  var medianVotes = null;
  var schulzeVotes = null;
  var colorOK = '#03ae13';
  var colorWrong = '#f11'

  function addVoterEntry(num) {
    var tableRow = '<tr class="options_content">';
    var nameId = 'name_' + num;
    var weightId = 'weight_' + num;
    var nameInput = '<td class="voter_name"><input type="text" id="' + nameId + '" maxlength="32" size="32"/></td>';
    var weightInput = '<td class="voter_weight"><input type="text" id="' + weightId + '" maxlength="3" size="3"/></td>';
    tableRow += (nameInput + weightInput + '</tr>');
    $('#voters').append(tableRow);
    return {nameId: nameId, weightId: weightId};
  }

  for (var i = 0; i < 5; i++) {
    addVoterEntry(i);
  }
  
  $('#add_voters').click(function (e) {
    var table = $('#voters');
    var firstId = table.get(0).rows.length - 1;
    for (var i = 0; i < 5; i++) {
      addVoterEntry(firstId + i);
    }
  });
  
  function setTableValues(vals) {
    $('#options .options_content').remove();
    for (var i = 0; i < vals.length; i++) {
      ids = addVoterEntry(i);
      var next = vals[i];
      $('#' + ids.nameId).val(next.name);
      $('#' + ids.weightId).val(next.weight);
    }
  }
  
  $('input[name=use_template]').change(function (e) {
    if ($('#stura_template').is(':checked')) {
      setTableValues(stura);
    } else if ($('#asta_template').is(':checked')) {
      setTableValues(asta);
    } else {
      var empty = {name: '', weight: ''};
      setTableValues([empty, empty, empty, empty, empty]);
    }
  });
  
  function getVoterContent() {
    result = []
    var length = $('#voters').get(0).rows.length - 1;
    for (var i = 0; i < length; i++) {
      name = $('#name_' + i).val();
      name = name.trim();
      weight = $('#weight_' + i).val();
      weight = weight.trim();
      if (name == '') {
        continue;
      }
      if (weight == '') {
        alert('Eintrag für "' + name + '" ohne Stimmgewichtung gefunden, wird ignoriert.');
        continue;
      }
      var w = parseInt(weight);
      if (isNaN(w)) {
        alert('Gewichtung für "' + name + '" ist keine Zahl, wird ignoriert.');
        continue;
      }
      result.push({name: name, weight: w});
    }
    return result;
  }

  function createMedianEntry(num, voter) {
    var tableRow = '<tr class="median_content">';
    var nameId = 'median_name_' + num;
    var weightId = 'median_weight_' + num;
    var medianId = 'median_vote_' + num;
    var voterTag = '<td class="voter_name" id="' + nameId + '">' + voter.name + '</td>';
    var weightTag = '<td class="voter_weight_median" id="' + weightId + '">' + voter.weight + '</td>';
    var voteInput = '<td class="median_vote_input"><input type="text" id="' + medianId + '" maxlength="16" size="16"/></td>';
    tableRow += (voterTag + weightTag + voteInput + '</tr>');
    $('#median_votes').append(tableRow);
    return {voter: voter, value: -1.0};
  }
  
  function createSchulzeEntry(num, voter) {
    var tableRow = '<tr class="schulze_content">';
    var nameId = 'schulze_name_' + num;
    var weightId = 'schulze_weight_' + num;
    var schulzeId = 'schulze_vote_' + num;
    var voterTag = '<td class="voter_name" id="' + nameId + '">' + voter.name + '</td>';
    var weightTag = '<td class="voter_weight_schulze" id="' + weightId + '">' + voter.weight + '</td>';
    var voteInput = '<td class="schulze_vote_input"><input type="text" id="' + schulzeId + '" maxlength="64" size="32"/></td>"';
    tableRow += (voterTag + weightTag + voteInput + '</tr>');
    $('#schulze_votes').append(tableRow);
    return {voter: voter, ranking: []};
  }

  $('#create_median').click(function (e) {
    var voters = getVoterContent();
    medianVotes = [];
    $('#median_votes .median_content').remove();
    for (var i = 0; i < voters.length; i++) {
      var e = createMedianEntry(i, voters[i]);
      medianVotes.push(e);
    }
  });
  
  $('#create_schulze').click(function (e) {
    var voters = getVoterContent();
    schulzeVotes = [];
    $('#schulze_votes .schulze_content').remove();
    for (var i = 0; i < voters.length; i++) {
      var e = createSchulzeEntry(i, voters[i]);
      schulzeVotes.push(e);
    }
  });
  
  $('#eval_median').click(function (e) {
    // assume that the table and medianVotes have the same size (minus table head)
    var name = $('#median_name').val().trim();
    var mi = new MedianInstance(name);
    if (medianVotes == null) {
      alert('Es wurde noch keine Instanz erzeugt.');
      return;
    }
    for (var i = 0; i < medianVotes.length; i++) {
      v = medianVotes[i];
      vote = $('#median_vote_' + i).val().trim();
      if (vote == '') {
        continue;
      }
      var val = parseFloat(vote);
      if (isNaN(val)) {
        alert('Betrag für "' + v.voter.name + '" ist keine Zahl, wird ignoriert.');
        continue;
      }
      v.value = val;
      mi.addVote(v);
    }
    var info = $('#median_info');
    if (mi.votes.length == 0) {
      info.text('Keine Eingaben gefunden.');
      info.css('color', colorWrong);
      return;
    }
    var percentRequired = 0.5;
    if ($('#three_quarters').prop('checked')) {
      percentRequired = 0.75;
    }
    // TODO param
    var res = mi.evalMedian(percentRequired);
    info.text('Ausgewertet wurden ' + mi.votes.length + ' Stimmberechtigte. Nötige Stimmen: ' + (res.requiredVotes + 1)
      + ' / ' + mi.weightSum + '. Genehmigter Betrag: ' + res.acceptedValue + '€.');
    info.css('color', colorOK);
  });
  
  $('#eval_schulze').click(function (e) {
    if (schulzeVotes == null) {
      alert('Es wurde noch keine Instanz erzeugt.');
      return;
    }
    var name = $('#schulze_name').val().trim();
    var itemText = $('#schulze_items').val();
    var itemsUnfiltered = itemText.split('\n');
    var items = [];
    for (var i = 0; i < itemsUnfiltered.length; i++) {
      var nxt = itemsUnfiltered[i].trim();
      if (nxt != '') {
        items.push(nxt);
      }
    }
    var si = new SchulzeInstance(name, items);
    for (var i = 0; i < schulzeVotes.length; i++) {
      var v = schulzeVotes[i];
      v.ranking = [];
      // parse the ranking in the box
      var rankingText = $('#schulze_vote_' + i).val().trim();
      var rankingStrings = rankingText.split(' ');
      for (var j = 0; j < rankingStrings.length; j++) {
        var nxt = rankingStrings[j];
        if (nxt == '') {
          continue;
        }
        var val = parseFloat(nxt);
        if (isNaN(val)) {
          alert('Eintrag in Ranking (Zeile) ' + (i + 1) + ' gefunden der keine Zahl ist: ' + nxt);
          continue;
        }
        val -= 1;
        v.ranking.push(val);
      }
      if (!si.addVote(v)) {
        return;
      }
    }
    var res = si.evalSchulze();
    // create html
    var ranks = res.ranks;
    var groupList = '<ol id="schulze_list">';
    for (var i = 0; i < ranks.length; i++) {
      var group = ranks[i];
      groupList += ('<li>' + (i + 1) + '. Gruppe</li><ul>');
      for (var j = 0; j < group.length; j++) {
        groupList += ('<li>' + si.items[group[j]] + '</li>')
      }
      groupList += '</ul>';
    }
    groupList += '</ol>';
    $('#schulze_list').remove();
    $('#schulze_result_info').append(groupList);
    
    // table d
    var d = res.d;
    var tableRow = $('<tr></tr>');
    tableRow.append('<td/>');
    for (var i = 0; i < d.length; i++) {
      var td = $('<td></td>');
      td.text('d[*, ' + (i + 1) + ']');
      tableRow.append(td);
    }
    var table = $('#schulze_d');
    table.append(tableRow);
    for (var i = 0; i < d.length; i++) {
      tableRow = $('<tr></tr>');
      var td = $('<td></td>');
      td.text('d[' + (i + 1) + ', *]');
      tableRow.append(td);
      for (var j = 0; j < d.length; j++) {
        td = $('<td></td>');
        if (i != j) {
          td.text(d[i][j]);
        }
        tableRow.append(td);
      }
      table.append(tableRow);
    }
    // now p
    var p = res.p;
    table = $('#schulze_p');
    tableRow = $('<tr></tr>');
    tableRow.append('<td/>');
    for (var i = 0; i < p.length; i++) {
      var td = $('<td></td>');
      td.text('p[*, ' + (i + 1) + ']');
      tableRow.append(td);
    }
    table.append(tableRow);
    for (var i = 0; i < p.length; i++) {
      tableRow = $('<tr></tr>');
      var td = $('<td></td>');
      td.text('p[' + (i + 1) + ', *]');
      tableRow.append(td);
      for (var j = 0; j < p.length; j++) {
        td = $('<td></td>');
        if (i != j) {
          td.text(p[i][j]);
        }
        tableRow.append(td);
      }
      table.append(tableRow);
    }
  });

  // TODO remove, useless...
  function WeightedVoter(name, weight) {
    this.name = name;
    this.weight = weight;
  }
  
  function MedianVote(voter, value) {
    this.voter = voter;
    this.value = value;
  }
  
  function MedianInstance(name) {
    this.name = name;
    this.votes = [];
    this.weightSum = 0;

    this.addVote = function(vote) {
      this.votes.push(vote);
      this.weightSum += vote.voter.weight;
    };

    this.evalMedian = function (percentRequired) {
      this.votes.sort(function (v1, v2) {
        return v2.value - v1.value;
      });
      // TODO is this correct?
      var requiredVotes = Math.floor(this.weightSum * percentRequired);
      var acceptedValue = null;
      var weightSoFar = 0;
      for (var i = 0; i < this.votes.length; i++) {
        var v = this.votes[i];
        weightSoFar += v.voter.weight;
        if (weightSoFar > requiredVotes) {
          acceptedValue = v.value;
          break;
        }
      }
      return {requiredVotes:requiredVotes, acceptedValue:acceptedValue};
    };
  }
  
  function SchulzeInstance(name, items) {
    this.name = name;
    this.items = items;
    this.votes = [];
    
    this.addVote = function(vote) {
      if (vote.ranking.length != this.items.length) {
        alert('Eingegebenes Ranking deckt nicht alle Eingaben ab');
        return false;
      }
      var voteEntry = [];
      for (var i = 0; i < vote.ranking.length; i++) {
        var r = vote.ranking[i];
        if ((r < 0) || (r >= this.items.length)) {
          alert('Ungültiger Wert für Position von Abstimmungsgegenstand ' + ': ' + (r + 1));
          return false;
        }
        voteEntry.push({id: i, rank: r});
      }
      voteEntry.sort(function (e1, e2) {
        return e1.rank - e2.rank;
      });
      this.votes.push({ranking: voteEntry, vote: vote});
      return true;
    };
    
    this.evalSchulze = function() {
      var d = this.computeD();
      var p = this.computeP(d);
      var ranks = this.rankP(p);
      return {d: d, p: p, ranks: ranks};
    };
    
    
    this.computeD = function() {
      d = [];
      for (var i = 0; i < this.items.length; i++) {
        var nxt = [];
        for (var j = 0; j < this.items.length; j++) {
          nxt.push(0);
        }
        d.push(nxt);
      }
      for (var i = 0; i < this.votes.length; i++) {
        var vote = this.votes[i].ranking;
        var outerPos = 0;
        while (outerPos < vote.length) {
          var outerVote = vote[outerPos];
          var outerRank = outerVote.rank;
          var innerPos = outerPos + 1;
          while (innerPos < vote.length) {
            var innerVote = vote[innerPos];
            if (outerRank != innerVote.rank) {
              d[outerVote.id][innerVote.id] += this.votes[i].vote.voter.weight;
            }
            innerPos++;
          }
          outerPos++;
        }
      }
      return d;
    };
    
    this.computeP = function(d) {
      var p = [];
      for (var i = 0; i < this.items.length; i++) {
        var nxt = [];
        for (var j = 0; j < this.items.length; j++) {
          nxt.push(0);
        }
        p.push(nxt);
      }
      // implemented as explained here <http://de.wikipedia.org/wiki/Schulze-Methode#Implementierung>
      for (var i = 0; i < this.items.length; i++) {
        for (var j = 0; j < this.items.length; j++) {
          if (i != j) {
            if (d[i][j] > d[j][i]) {
              p[i][j] = d[i][j];
            } else {
              p[i][j] = 0;
            }
          }
        }
      }
      for (var i = 0; i < this.items.length; i++) {
        for (var j = 0; j < this.items.length; j++) {
          if (i != j) {
            for (k = 0; k < this.items.length; k++) {
              if (i != k) {
                if (j != k) {
                  p[j][k] = Math.max(p[j][k], Math.min(p[j][i], p[i][k]));
                }
              }
            }
          }
        }
      }
      return p;
    };
    
    this.rankP = function(p) {
      // implemented as _rank_p here <https://github.com/mgp/schulze-method/blob/master/schulze.py>
      var result = [];
      var candidateWins = {};
      for (var i = 0; i < p.length; i++) {
        var numWins = 0;
        for (var j = 0; j < p.length; j++) {
          if (i != j) {
            var candidateOneScore = p[i][j];
            var candidateTwoScore = p[j][i];
            if (candidateOneScore > candidateTwoScore) {
              numWins++;
            }
          }
        }
        var lst = null;
        if (!candidateWins.hasOwnProperty(numWins)) {
          lst = [];
          candidateWins[numWins] = lst;
        } else {
          lst = candidateWins[numWins];
        }
        lst.push(i);
      }
      var keys = [];
      for (var key in candidateWins) {
        keys.push(key);
      }
      keys.sort(function (k1, k2) {
        return k2 - k1;
      });
      for (var i = 0; i < keys.length; i++) {
        result.push(candidateWins[keys[i]]);
      }
      return result;
    };
    
  }
});
